package org.acooke.uykfd.db

import java.net.URLDecoder
import java.sql.Connection
import java.util.regex.Pattern

import org.apache.empire.db.DBCommand
import org.apache.empire.db.DBReader
import org.apache.empire.db.DBRecord
import org.apache.empire.db.DBTable

import org.hsqldb.jdbcDriver


/**
 * An interface to the CANONICAL table, which contains string values
 * in a standardised format.
 */
trait CanonicalRow
  extends IdValueRow

trait CanonicalTable
  extends IdValueTable[CanonicalRow, CanonicalTable, Schema.Canonicals] {
 
  /** Various character maps for "generalizing" names. */
  val IGNORE_START = Set("the", "el", "la", "los", "las", "le", "les")
  val ACCENTS = Map("àáâãäå" -> "a",
                    "èéêë" -> "e",
                    "ìíîï" -> "i",
                    "òóôõöø" -> "o",
                    "ùúûü" -> "u",
                    "ýÿ" -> "y",
                    "ñ" -> "n",
                    "ç" -> "c")
  val LIGATURES = Map("œ" -> "oe",
                      "ß" -> "ss",
                      "æ" -> "ae",
                      "-" -> "")
  val ACCEPT = Pattern.compile("^[\\d\\w \\-,\\./\\\\&\\+;:'\"]$")

  def canonize(text: String, ignoreStart: Boolean = true, toSpace: String = "[,\\.-/ \\\\]"): String = {
    // sometimes we have escape sequences!
    val decoded = try {
      URLDecoder.decode(text, "utf-8")
    } catch {
      case e: Exception => text
    }
    // this drops "foreign" characters
    def clean_letter(letter: String): String = {
      var clean = letter
      for (chars <- ACCENTS.keysIterator) if (chars.indexOf(letter) > -1) clean = ACCENTS(chars)
      if (LIGATURES.contains(letter)) clean = LIGATURES(letter)
      if (! ACCEPT.matcher(clean).matches()) clean = ""
      clean
    }
    def clean_word(word: String) = word map {c => clean_letter(""+c)} reduceLeft (_+_)
    // exactly what should be a read as a space?  there is no perfect answer...
    var words = decoded.toLowerCase.split(toSpace).toList.filter(word => word.length > 0).map(clean_word)
    if (words.length > 1 && ignoreStart && IGNORE_START.contains(words(0))) words = words.tail
    // reassemble with spaces
    words reduceLeft (_+ " " +_)
  }

  def split(list: String) = {
    list.toLowerCase.split("""\s*(,|\+|&)\s*|\s+(and|et|y|e|presents|with|feat|featuring)\s+""").toList
  }

  def fromValue(value: String, cnxn: Connection, 
                ignoreStart: Boolean = true, toSpace: String = "[,\\.-/ \\\\]"): CanonicalRow

}

private class CanonicalFactory
  extends IdValueFactory[CanonicalRecord, CanonicalTable, Schema.Canonicals](Schema.CANONICALS)
  with CanonicalTable {

  def rowFactory = new CanonicalRecord

  /**
   * If ignoreStart is true, certain common initial words ("the", etc) will be dropped.
   */
  override def fromValue(value: String, cnxn: Connection, 
                         ignoreStart: Boolean = true, toSpace: String = "[,\\.-/ \\\\]") = {
    super.fromValue(canonize(value, ignoreStart, toSpace), cnxn)
  }

}

private class CanonicalRecord
  extends IdValueRecord[CanonicalRecord, CanonicalTable, Schema.Canonicals](Schema.CANONICALS)
  with CanonicalRow
